#include "GeneradorIndice.h"
#include "abb.h"
#include "ManejadorArchivos.h"
#include "Parser.h"
#include "ElementoParseado.h"
#include "vector_dinamico.h"
#include "FrontCoder.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* *****************************************************************
 *                DEFINICION DE LOS TIPOS DE DATOS
 * *****************************************************************/

struct _GeneradorIndice {
     ManejadorArchivos_t* archivo;
     int numeroArchivo;
};

/* ******************************************************************
 *            			PRIMITIVAS AUXILIARES						*
 * *****************************************************************/
					  /* FUNCIONES INTERNAS */
 
void abb_destruir_dato (void *dato) {
	ElementoParseado_destruir ( (ElementoParseado_t*) dato );
}

void quemar_arbol (void* dato) {
	abb_destruir ( (abb_t*) dato );
}

/* Devuelve un vector dinámico de CANT_ARCH posiciones con un ABB en cada
 * una de ellas. Devuelve NULL en caso de error. */
vector_dinamico_t* generar_arboles () {
	vector_dinamico_t* vector = vector_crear (CANT_ARCH, quemar_arbol);
	if (!vector) return NULL;
	
	for ( int i = 0 ; i < CANT_ARCH ; i++ ) {
		abb_t* arbol = abb_crear (strcmp, abb_destruir_dato);
		if (!arbol) {
			vector_destruir (vector);
			return NULL;
		}
		vector_guardar (vector, i, arbol);
	}
	return vector;
}

/* Devuelve el ABB almacenado en el vector dinámico que corresponda a la
 * palabra pasada. Devuelve NULL en caso de error. */
abb_t* obtener_arbol (vector_dinamico_t* vector, char* palabra) {
	if ( (!vector) || (!palabra) ) return NULL;
	
	// num e [48;57] ; letra e [97;122]
	// letra: pos = letra - ascii(a)
	// num: pos = (num - ascii(0)) + (ascii(z)-ascii(a)+1)
	char caracter = palabra[0];
	if ( (caracter >= 97) && (caracter <= 122) ) {
		int pos = caracter - 'a';
		return vector_obtener (vector, pos);
	}
	if ( (caracter >= 48) && (caracter <= 57) ) {
		int pos = ( (caracter - '0') + ('z' - 'a' + 1) );
		return vector_obtener (vector, pos);
	}
	return NULL;
}

/* Parsea todo el archivo mientras va llenando todos los árboles con nodos del
 * tipo ElementoParseado_t.
 * Post: Devuelve el vector lleno de árboles o NULL en caso de error. */
vector_dinamico_t* GeneradorIndice_generar(GeneradorIndice_t* indice) {
	vector_dinamico_t* arboles = generar_arboles ();
	if (!arboles) return NULL;
		
	int ultimaLeida = 0;
	while( !(ManejadorArchivos_terminoDeLeer(indice->archivo)) ) {
			char* cadenaAParsear = ManejadorArchivos_leer (indice->archivo);
			Parser_t* parser = Parser_crear (cadenaAParsear, indice->numeroArchivo, 0, ultimaLeida);
			
			while( !(Parser_TerminoDeParsear (parser)) ) {
				char* palabra = Parser_parsear(parser);
				ultimaLeida = Parser_numeroDePalabra (parser);
				
				if(strlen(palabra) == 0) {
					free(palabra);
					continue;
				}
				
				abb_t* arbol = obtener_arbol (arboles, palabra);
				ElementoParseado_t* elem = abb_obtener (arbol, palabra);
				if (elem != NULL) {
					ElementoParseado_agregarUbicacion (elem, ultimaLeida, indice->numeroArchivo);
				} else {
					int largo = Parser_LargoUltimaPalabra (parser);
					elem = ElementoParseado_crear (palabra, largo);
					ElementoParseado_agregarUbicacion (elem, ultimaLeida, indice->numeroArchivo);
					abb_guardar (arbol, palabra, elem);
				}
				free (palabra);
			}
			Parser_destruir (parser);
            free (cadenaAParsear);
	}
	
	return arboles;
}

/* Crea un string concatenando: letra + tipo + direccion + extension */
char* crear_string_direccion (char letra, char* tipo, char* direccion, char* ext) {
	
	size_t tam_tipo = strlen (tipo);
	size_t tam_ext = strlen (ext);
	size_t tam_dir = strlen (direccion);
	
	/* El + 2 corresponde uno por la letra y el otro por el caracter finalizador de strings \0 */
	char* direccionCompleta = malloc ( (tam_dir + tam_tipo + tam_ext + 2) * sizeof(char) );
	if (!direccionCompleta) return NULL;
	
	memset ( direccionCompleta , 0, (tam_dir + tam_tipo + tam_ext + 2) );
	direccionCompleta[0] = letra;
	strcat (direccionCompleta, tipo);
	strcat (direccionCompleta, direccion);
	strcat (direccionCompleta, ext);
	
	return direccionCompleta;
}

int escribir_datos (FILE* datos, ElementoParseado_t* elemento) {
	// cantidad de documentos
	int* num = malloc (sizeof(int));
	*num = ElementoParseado_verCantidadUbicaciones (elemento);
	fwrite ( (const void*) num, sizeof (int), 1, datos );
	int cant_enteros = 1;
	
	// Documentos
	vector_dinamico_t* vector = ElementoParseado_verArchivos (elemento);
	if (!vector) {
		free (num);
		return -1;
	}
	int* aux;
	for ( int i = 0 ; i < *num ; i++ ) {
		aux = vector_obtener (vector,i);
		fwrite ( (const void*) aux, sizeof (int), 1, datos );
		cant_enteros++;
	}
	vector_destruir (vector);	
	
	// Frecuencias del término en cada documento y sus posiciones.
	lista_iter_t* iter = lista_iter_crear ( ElementoParseado_verUbicaciones (elemento) );
	if (!iter) {
		free (num);
		return -1;
	}
	
	Ubicador_t* ubicacion;
	while ( !(lista_iter_al_final (iter)) ) {
		ubicacion = lista_iter_ver_actual (iter);
		vector = Ubicador_verPosicionesEnVector (ubicacion);
		// frecuencia
		*num = vector_cantidad (vector);
		fwrite ( (const void*) num, sizeof (int), 1, datos );
		cant_enteros++;
		// posiciones
		for ( int j = 0 ; j < *num ; j++ ) {
			aux = vector_obtener (vector, j);
			fwrite ( (const void*) aux, sizeof (int), 1, datos );
			cant_enteros++;
		}
		lista_iter_avanzar (iter);
		vector_destruir (vector);
	}
	lista_iter_destruir(iter);
	
	free (num);
	return ( cant_enteros * 4 );
}

/* Funcion diseñada para pasarle al abb de tal forma que escriba en el archivo correspondiente. */
bool escribir_archivo (const char *clave, void *dato, void *extra) {
	ElementoParseado_t* elemento = (ElementoParseado_t*) dato;
	vector_dinamico_t* parametros = (vector_dinamico_t*) extra;
	FILE* lexico = vector_obtener (parametros, 0);
	FILE* datos = vector_obtener (parametros, 1);
	char* ultimoLeido = vector_obtener (parametros, 2);
	int* offsetActual = vector_obtener (parametros, 3);
	
	int* num = malloc (sizeof(int));
	if (!num) return NULL;
		
	/* Repetidos */
	*num = obtener_repetidos (clave, ultimoLeido);
	fwrite ( (const void*) num, sizeof (int), 1, lexico );
	
	/* Distintos */
	*num = obtener_distintos (clave, ultimoLeido);
	fwrite ( (const void*) num, sizeof (int), 1, lexico );
	
	/* Caracteres */
	char* truncado = obtener_truncado (clave, ultimoLeido);
	fwrite ( (const void*) truncado, sizeof (char), strlen (truncado), lexico );
	free(truncado);
	
	/* Offset */
	fwrite ( (const void*) offsetActual, sizeof (int), 1, lexico );
	
	*offsetActual += escribir_datos (datos, elemento);
	
	vector_guardar (parametros, 2, (void*) clave);
	free (num);
	
	return true;
}


/* Escribo en los archivos en el formato de front-coding */
bool generar_front_coding ( FILE* lexico, FILE* datos, abb_t* abb ) {
	vector_dinamico_t* parametros = vector_crear (4, NULL);
	if (!parametros) return NULL;
	
	int* num = malloc (sizeof (int) );
	if (!num) {
		free (parametros);
		return NULL;
	}
	
	*num = 0;
	
	vector_guardar (parametros, 0, lexico);
	vector_guardar (parametros, 1, datos);
	vector_guardar (parametros, 2, NULL);
	vector_guardar (parametros, 3, num);
	
	
	abb_in_order (abb, escribir_archivo, parametros);
	
	free(num);
	vector_destruir (parametros);
	return true;
}
/* Genera y llena todos los archivos correspondientes a cada caracter y numero.
 * Post: Todos los archivos fueron creados y estan cargados. */
bool generar_archivos (char* direccion, vector_dinamico_t* arboles) {

	char alphanum[] = NOMBRES_ARCH;
	/* Todas las letras */
	for (int i = 0; i < CANT_ARCH; i++) {
		
		/* Construyo el string que sera el nombre del archivo */
		char* direccionLexico = crear_string_direccion (alphanum[i], "_lexico_", direccion, ".dat");
		if (!direccionLexico) return NULL;
		
		char* direccionDatos = crear_string_direccion (alphanum[i], "_datos_", direccion, ".dat");
		if (!direccionDatos) {
			free (direccionLexico);
			return NULL;
		}
		
		/* Creo el archivo con el nombre correcto */
		FILE* lexico = fopen (direccionLexico, "wb");
		if (!lexico) {
			free (direccionLexico);
			free (direccionDatos);
			return false;
		}
		
		FILE* datos = fopen (direccionDatos, "wb");
		if (!datos) {
			fclose (lexico);
			free (direccionLexico);
			free (direccionDatos);
			return false;
		}
		
		generar_front_coding (lexico, datos, vector_obtener(arboles, i) );
		
		fclose (lexico);
		fclose (datos);
		free (direccionLexico);
		free (direccionDatos);
	}
	
	return true;
}

/* 'nombre' debe tener el formato: "_nombre_" */
vector_dinamico_t* dame_ruta (GeneradorIndice_t* indice, char* nombre) {
	if (!indice) return NULL;
	char alphanum[] = NOMBRES_ARCH;
	char* direccion = ManejadorArchivos_nombreDelArchivoSinExt (indice->archivo);
	vector_dinamico_t* vector = vector_crear (CANT_ARCH, free);
	if (!vector) return NULL;
	
	/* Todas las letras */
	for (int i = 0; i < CANT_ARCH; i++) {		
		/* Construyo el string que sera el nombre del archivo */
		char* ruta = crear_string_direccion (alphanum[i], nombre , direccion, ".dat");
		if (!ruta) {
			vector_destruir (vector);
			return NULL;
		}
		vector_guardar (vector, i, ruta);
	}
	return vector;
}

//************

/* ******************************************************************
 *             		IMPLEMENTACION DE PRIMITIVAS					*
 * *****************************************************************/

// Crea un GeneradorIndice.
// Pre: 'nombreDelArchivo' es la ruta al archivo a indexar.
// Post: Devuelve un GeneradorIndice o NULL en caso de error.
GeneradorIndice_t* GeneradorIndice_crear (char* nombreDelArchivo, int numeroDelArchivo) {
	GeneradorIndice_t* indice = malloc(sizeof(GeneradorIndice_t));
	if (!indice) return NULL;
	
	indice->archivo = ManejadorArchivos_crear (nombreDelArchivo);
	if (!(indice->archivo)) {
		free (indice);
		return NULL;
	}
	indice->numeroArchivo = numeroDelArchivo;
	
	return indice;
}

// Indiza el archivo.
// Post: Se creó un archivo de extensión .ind con el nombre que se
// pasó por parámetro en la creación del generador y se devuelve true
// si el proceso fue exitoso. False, en caso contrario.
bool GeneradorIndice_indexar (GeneradorIndice_t* indice) {
	vector_dinamico_t* arboles = GeneradorIndice_generar (indice);
	if (!arboles) return false;
	
	char* direccion = ManejadorArchivos_nombreDelArchivoSinExt (indice->archivo);
	
	bool ok = generar_archivos (direccion, arboles);
	if (!ok) {
		vector_destruir (arboles);
		return false;
	}
	
	vector_destruir (arboles);
	return true;
}

// Devuelve un vector de CANT_ARCH posiciones con las rutas correspondientes
// a los léxicos del índice.
vector_dinamico_t* GeneradorIndice_verRutasLexico (GeneradorIndice_t* indice) {
	return ( dame_ruta (indice, "_lexico_") );
}

// Devuelve un vector de CANT_ARCH posiciones con las rutas correspondientes
// a los datos del índice.
vector_dinamico_t* GeneradorIndice_verRutasDatos (GeneradorIndice_t* indice) {
	return ( dame_ruta (indice, "_datos_") );
}

// Destruye el GeneradorIndice.
void GeneradorIndice_destruir (GeneradorIndice_t * indice) {
	ManejadorArchivos_destruir (indice->archivo);
	free(indice);		
}

